/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.tools;

import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;

import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;

/**
 * A FunctionEditor for UserFunctions.
 *
 * @author Douglas Brown
 */
public class UserFunctionEditor extends FunctionEditor {
	
	private UserFunction[] mainFunctions = new UserFunction[0];
	private String[] defaultVariableNames = new String[] {"x"}; //$NON-NLS-1$
	protected boolean parametersValid = true;

	/**
	 * Constructor.
	 */
  public UserFunctionEditor() {
  	super();
  }

  /**
   * Returns the main user functions.
   *
   * @return UserFunction[]
   */
  public UserFunction[] getMainFunctions() {
    return mainFunctions;
  }

  /**
   * Sets the main user functions.
   *
   * @param functions UserFunction[]
   */
  public void setMainFunctions(UserFunction[] functions) {
  	for (int i = 0; i < functions.length; i++) {
  		boolean found = false;
  		for (int row = 0; row < objects.size(); row++) {
  			Object next = objects.get(row);
  			if (getName(next).equals(functions[i].getName())) {
  				objects.remove(next);
  				objects.add(row, functions[i]);
  				found = true;
  			}
  		}
  		if (!found) addObject(functions[i], false);
  	}
    mainFunctions = functions;
  	defaultVariableNames = functions[0].getIndependentVariables();
  }

  /**
   * Returns supporting functions
   *
   * @return an array of UserFunctions
   */
  public UserFunction[] getSupportFunctions() {
  	ArrayList temp = new ArrayList();
  	for (Iterator it = objects.iterator(); it.hasNext();) {
  		Object next = it.next();
  		if (!isMainFunction(next)) temp.add(next);
  	}
  	return (UserFunction[])temp.toArray(new UserFunction[0]);
  }
  
  private boolean isMainFunction(Object obj) {
  	for (int i = 0; i < mainFunctions.length; i++) {
  		if (obj == mainFunctions[i]) return true;
  	}  		
  	return false;
  }

  /**
   * Returns the name of the object.
   *
   * @param obj the object
   * @return the name
   */
  public String getName(Object obj) {
    return obj == null? null: ((UserFunction)obj).getName();
  }

  /**
   * Returns the expression of the object.
   *
   * @param obj the object
   * @return the expression
   */
  public String getExpression(Object obj) {
    return obj == null? null: ((UserFunction)obj).getInputString();
  }

	/**
	 * Determines if an object's name is editable.
	 * 
	 * @param obj the object
	 * @return true if the name is editable
	 */
	public boolean isNameEditable(Object obj) {
		return ((UserFunction)obj).isNameEditable();
	}
	
	/**
	 * Determines if an object's expression is editable.
	 * 
	 * @param obj the object
	 * @return true if the expression is editable
	 */
	public boolean isExpressionEditable(Object obj) {
		return true;
	}
	
	/**
	 * Determines if an object is important. Important objects cannot be cut
	 * even if they are editable.
	 * 
	 * @param obj the object
	 * @return true if important
	 */
	protected boolean isImportant(Object obj) {
		for (int i = 0; i < mainFunctions.length; i++) {
			if (mainFunctions[i] == obj) return true;
		}
		return false;
	}
	
	/**
	 * Evaluates all current objects.
	 */
	public void evaluateAll() {
		super.evaluateAll();
		ParamEditor paramEditor = getParamEditor();
		if (!parametersValid && paramEditor != null) 
			paramEditor.evaluateAll();
		for (int i = 0; i < evaluate.size(); i++) {
    	UserFunction f = (UserFunction)evaluate.get(i);
  		if (!parametersValid && paramEditor != null) {
      	f.setParameters(paramEditor.getNames(), paramEditor.getValues());
  		}
    	f.setExpression(f.getInputString(), f.getIndependentVariables());
		}
		parametersValid = true;
	}

  /**
   * Informs an object about other objects referenced in its expression.
   */
  protected void setReferences(Object obj, List ref) {
  	UserFunction f = (UserFunction)obj;
  	UserFunction[] references = (UserFunction[])ref.toArray(new UserFunction[0]);
  	f.setReferences(references);
  }
  
	/**
	 * Adds an object.
	 * 
	 * @param obj the object
	 * @param postEdit true to post an undoable edit
	 */
	public Object addObject(Object obj, int row, 
				boolean postEdit, boolean firePropertyChange) {
		obj = super.addObject(obj, row, postEdit, firePropertyChange);
		if (obj != null) {
			firePropertyChange("function", null, obj); //$NON-NLS-1$
		}
		return obj;
	}

	/**
	 * Removes an object.
	 * 
	 * @param obj the object to remove
	 * @param postEdit true to post an undoable edit
	 * @return the removed object
	 */
	public Object removeObject(Object obj, boolean postEdit) {
		obj = super.removeObject(obj, postEdit);
		if (obj != null) {
			firePropertyChange("function", obj, null); //$NON-NLS-1$
		}
		return obj;
	}
	
  /**
   * Returns a tooltip for the object.
   *
   * @param obj the object
   * @return the tooltip
   */
  public String getTooltip(Object obj) {
    return obj == null? null: ((UserFunction)obj).getDescription();
  }
  
  /**
   * Returns true if a name is forbidden or in use.
   *
   * @param obj the object (may be null) 
   * @param name the proposed name for the object
   * @return true if disallowed
   */
  protected boolean isDisallowedName(Object obj, String name) {
  	boolean disallowed = super.isDisallowedName(obj, name);
  	if (obj != null) {
  		String var = ((UserFunction)obj).getIndependentVariable();
      disallowed = disallowed || var.equals(name);  		
  	}
    return disallowed;
  }

	/**
   * Returns a String with the names of variables available for expressions.
   */
  protected String getVariablesString() {
    StringBuffer vars = new StringBuffer(" "); //$NON-NLS-1$
    int init = vars.length();
  	boolean firstItem = true;
    UserFunction f = (UserFunction)getSelectedObject();
    if (f != null) {
    	String[] s = f.getIndependentVariables();
    	for (int i = 0; i < s.length; i++) {
        if (!firstItem) vars.append(", "); //$NON-NLS-1$
  			vars.append(s[i]);
  			firstItem = false;  			
    	}
    }
		List namesToSkip = new ArrayList();
		namesToSkip.add(getName(getSelectedObject()));
		for (int i = 0; i < mainFunctions.length; i++) {
			namesToSkip.add(getName(mainFunctions[i]));
		}
		for (int i = 0; i < names.length; i++) {
			if (namesToSkip.contains(names[i])) continue;
      if (!firstItem) vars.append(", "); //$NON-NLS-1$
			vars.append(names[i]);
			firstItem = false;  			
		}
		// add parameters, if any
		String[] paramNames = paramEditor.getNames();
		for (int i = 0; i < paramNames.length; i++) {
      if (!firstItem) vars.append(", "); //$NON-NLS-1$
			vars.append(paramNames[i]);
			firstItem = false;  			
		}
		if (vars.length() == init) 
			return ToolsRes.getString("FunctionPanel.Instructions.Help"); //$NON-NLS-1$
		return ToolsRes.getString("FunctionPanel.Instructions.ValueCell") //$NON-NLS-1$
				+":"+vars.toString(); //$NON-NLS-1$
  }
  
  /**
   * Returns true if the object's expression is invalid.
   */
  protected boolean isInvalidExpression(Object obj) {
  	UserFunction f = (UserFunction)obj;
  	return !f.getExpression().equals(f.getInputString());
  }
  
  /**
   * Creates an object with specified name and expression.
   * This modifies and returns the input UserFunction unless null. 
   *
   * @param name the name
   * @param expression the expression
   * @param obj ignored
   * @return the object
   */
  protected Object createObject(String name, String expression, Object obj) {
  	UserFunction f = (UserFunction)obj;
  	if (f != null
  			&& f.getName().equals(name) 
  			&& f.getInputString().equals(expression))
  		return f;
		if (f == null) {
			f = new UserFunction(name);
			f.setParameters(paramEditor.getNames(), paramEditor.getValues());
			f.setExpression(expression, defaultVariableNames);
		}
		else if (!f.getName().equals(name)) {
			f.setName(name);
		}
		else {
			f.setParameters(paramEditor.getNames(), paramEditor.getValues());
			f.setExpression(expression, defaultVariableNames);
		}
    return f;
  }

//__________________________ static methods ___________________________

  /**
   * Returns an ObjectLoader to save and load data for this class.
   *
   * @return the object loader
   */
  public static XML.ObjectLoader getLoader() {
    return new Loader();
  }

  /**
   * A class to save and load data for this class.
   */
  static class Loader implements XML.ObjectLoader {

    public void saveObject(XMLControl control, Object obj) {
      UserFunctionEditor editor = (UserFunctionEditor)obj;
      UserFunction[] functions = editor.getMainFunctions();
      control.setValue("main_functions", functions); //$NON-NLS-1$
      functions = editor.getSupportFunctions();
      if (functions.length > 0)
        control.setValue("support_functions", functions); //$NON-NLS-1$
    }

    public Object createObject(XMLControl control){
      return new UserFunctionEditor();
    }

    public Object loadObject(XMLControl control, Object obj) {
      UserFunctionEditor editor = (UserFunctionEditor)obj;
      UserFunction[] functions = (UserFunction[])control.getObject("main_functions"); //$NON-NLS-1$
      editor.setMainFunctions(functions);
    	int row = functions.length;
      functions = (UserFunction[])control.getObject("support_functions"); //$NON-NLS-1$
      if (functions != null) {
      	for (int i = 0; i < functions.length; i++) {
      		editor.addObject(functions[i], row+i, false, false);
      	}
      }
      return obj;
    }
  }

}
	

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
